package cn.stimd.spring.context.annotation;

import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * ConfigurationClass表示使用@Configuration修饰的配置类，包括一系列使用@Bean注解修饰的工厂方法。
 * 1. 如果有多个内部类，则以扁平化的方式管理所有的Bean方法
 * 2. 重写equals和hashcode方法，添加到Set集合时去重
 * 3. 每个配置类都对应一个ConfigurationClass，除了一开始来自ConfigurationClassPostProcessor的配置类，
 *    还有通过ComponentScan组件扫描、Import导入，以及内部配置类
 * <p>
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ConfigurationClass {
    private final AnnotationMetadata metadata;
    private final Resource resource;
    private String beanName;
    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();


    public ConfigurationClass(AnnotationMetadata metadata, String beanName) {
        this.metadata = metadata;
        this.resource = new DescriptiveResource(metadata.getClassName());
        this.beanName = beanName;
    }

    public ConfigurationClass(Class<?> clazz, String beanName) {
        Assert.hasText(beanName, "Bean name must not be null");
        this.metadata = new StandardAnnotationMetadata(clazz, true);
        this.resource = new DescriptiveResource(clazz.getName());
        this.beanName = beanName;
    }

    public ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
        this.metadata = metadataReader.getAnnotationMetadata();
        this.resource = metadataReader.getResource();
        this.importedBy.add(importedBy);
    }

    /**
     * 表示使用@Import导入的类，或作为配置类的内部类自动处理
     */
    public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
        this.metadata = new StandardAnnotationMetadata(clazz, true);
        this.resource = new DescriptiveResource(clazz.getName());
        this.importedBy.add(importedBy);
    }


    /**
     * 表示该ConfigurationClass是否由其他配置类通过@Import的方式导入的
     */
    public boolean isImported() {
        return !this.importedBy.isEmpty();
    }

    public Set<ConfigurationClass> getImportedBy() {
        return this.importedBy;
    }

    public AnnotationMetadata getMetadata() {
        return this.metadata;
    }

    public Resource getResource() {
        return this.resource;
    }

    public String getSimpleName() {
        return ClassUtils.getShortName(getMetadata().getClassName());
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getBeanName() {
        return this.beanName;
    }

    public void addBeanMethod(BeanMethod method) {
        this.beanMethods.add(method);
    }

    public Set<BeanMethod> getBeanMethods() {
        return this.beanMethods;
    }

    public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
    }

    public Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
        return this.importBeanDefinitionRegistrars;
    }

    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof ConfigurationClass &&
                getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
    }

    @Override
    public int hashCode() {
        return getMetadata().getClassName().hashCode();
    }

    @Override
    public String toString() {
        return "ConfigurationClass: beanName '" + this.beanName + "', " + this.resource;
    }
}
